////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __WN_CONCURRENCY_SEMAPHORE_INL__
#define __WN_CONCURRENCY_SEMAPHORE_INL__

#include "WNCore/inc/WNPlatform.h"
#include "WNCore/inc/WNTypes.h"
#include "WNCore/inc/WNAssert.h"

#ifdef _WN_WINDOWS
    #include <windows.h>
    #include <limits.h>
#elif defined _WN_LINUX
    #include <semaphore.h>
#endif

namespace WNConcurrency {
    WN_INLINE WNSemaphore::WNSemaphore(WN_UINT32 _count) {
        #ifdef _WN_WINDOWS
            mHandle = CreateSemaphore(WN_NULL, _count, LONG_MAX, WN_NULL);

            WN_RELEASE_ASSERT(mHandle != WN_NULL);
        #elif defined _WN_LINUX
            WN_RELEASE_ASSERT(sem_init(&mSemaphore, 0, _count) == 0);
        #endif
    }

    WN_INLINE WNSemaphore::~WNSemaphore() {
        #ifdef _WN_WINDOWS
            WN_RELEASE_ASSERT(CloseHandle(mHandle));
        #elif defined _WN_LINUX
            WN_RELEASE_ASSERT(sem_destroy(&mSemaphore) == 0);
        #endif
    }

    WN_INLINE WN_VOID WNSemaphore::Lock() {
        #ifdef _WN_WINDOWS
            WN_RELEASE_ASSERT(WaitForSingleObject(mHandle, INFINITE) != WAIT_OBJECT_0);
        #elif defined _WN_LINUX
            WN_RELEASE_ASSERT(sem_wait(&mSemaphore) == 0);
        #endif
    }

    WN_INLINE WN_BOOL WNSemaphore::TryLock() {
        #ifdef _WN_WINDOWS
            return(WaitForSingleObject(mHandle, 0) != WAIT_TIMEOUT ? WN_TRUE : WN_FALSE);
        #elif defined _WN_LINUX
            return(sem_trywait(&mSemaphore) == 0 ? WN_TRUE : WN_FALSE);
        #endif
    }

    WN_INLINE WN_VOID WNSemaphore::Unlock() {
        #ifdef _WN_WINDOWS
            WN_RELEASE_ASSERT(ReleaseSemaphore(mHandle, 1, WN_NULL));
        #elif defined _WN_LINUX
            WN_RELEASE_ASSERT(sem_post(&mSemaphore) == 0);
        #endif
    }

    WN_INLINE WN_INT32 WNSemaphore::GetCount() const {
        #ifdef _WN_WINDOWS
            LONG currentCount = -1;

            WN_RELEASE_ASSERT(ReleaseSemaphore(mHandle, 0, &currentCount));

            return(static_cast<WN_UINT32>(currentCount));
        #elif defined _WN_LINUX
            WN_INT32 currentCount = -1;
            sem_t* sem = const_cast<sem_t*>(&mSemaphore); 
            WN_RELEASE_ASSERT(sem_getvalue(sem, &currentCount) == 0);

            return(currentCount);
        #endif
    }

    WN_INLINE WNSemaphoreScopeLock::WNSemaphoreScopeLock(WNSemaphore& _semaphore) :
        mLocked(WN_TRUE),
        mSemaphore(_semaphore) {
        mSemaphore.Lock();
    }

    WN_INLINE WNSemaphoreScopeLock::~WNSemaphoreScopeLock() {
        if (mLocked) {
            mSemaphore.Unlock();

            mLocked = WN_FALSE;
        }

    }
}

#endif // __WN_CONCURRENCY_SEMAPHORE_INL__
